/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: AsyncCommand.java,v $
 *
 */
package com.cidero.util;

import java.util.logging.Logger;

/**
 * Asynchronous command class. Used to provide isolation between a 
 * blocking command path (like system-supplied HTTP commands) and
 * a thread that does not wish to block for arbitrary lengths of time.
 * (allows for timeout detection)
 *
 */
public class AsyncCommand
{
  private static Logger logger =
    Logger.getLogger("com.cidero.util.AsyncCommand");

  SynchronizedQueue requestQueue;
  SynchronizedQueue responseQueue;
  int sendSeqNum = 0;
  int recvSeqNum = 0;
  int currentRequestSeqNum;
  
  /**
   * Constructor
   */
  public AsyncCommand( int maxSize )
  {
    requestQueue = new SynchronizedQueue( maxSize );
    responseQueue = new SynchronizedQueue( maxSize );
  }
  
  /**
   *  Send a request and wait the specified amount of time for a response
   *  This method is used by multiple threads, so it is synchronized
   *  (process one outstanding request at a time)
   */
  public synchronized Object send( Object obj, int timeoutMillisec )
  {
    // Add sequence number to send object so corresponding response can 
    // be identified
    TaggedMsg msg = new TaggedMsg( sendSeqNum, obj );
    
    if( ! requestQueue.add( msg, timeoutMillisec ) ) 
      return null;

    int responseSeqNum = sendSeqNum;
    
    // Bump sequence number. Wrap-around at 16 bits (arbitrary, but 
    // don't need 32 -bit counter
    sendSeqNum = (sendSeqNum + 1) & 0xffff;
    
    if( timeoutMillisec == 0 )
      return null;

    while( true )
    {
      msg = (TaggedMsg)responseQueue.get( timeoutMillisec );
      if( msg == null )
        return null;
      
      if( msg.getId() == responseSeqNum )
        return msg.getObject();

      logger.warning( "Discarding reponse - Expected seqNum " + 
                      responseSeqNum + " got " + msg.getId() );
    }
  }

  /**
   * Receive a request from a sender, timing out after the specified 
   * interval
   */
  public Object receiveRequest( int timeoutMillisec )
  {
    TaggedMsg msg = (TaggedMsg)requestQueue.get( timeoutMillisec );
    if( msg == null )
      return null;
    
    currentRequestSeqNum = msg.getId();
    
    return msg.getObject();
  }

  /**
   *  Send a request and wait the specified amount of time for a response
   */
  public boolean sendResponse( Object obj, int timeoutMillisec )
  {
    // Tag response with sequence number of last received request.
    // Note: This assumes single-thread command processing!  TODO - enhance?
    TaggedMsg msg = new TaggedMsg( currentRequestSeqNum, obj );
    
    return responseQueue.add( msg, timeoutMillisec );
  }
  
}
